/*
 * $Id: SttpBase.cc,v 1.5 2006-02-14 06:02:08 bacon Exp $
 */

#include <xpp/net/SttpBase.h>
#include <xpp/bas/String.h>
#include <xp/bas/assert.h>
#include <xp/bas/utf8.h>
#include <xp/bas/memory.h>

#define GET_CHAR      if (get_char()  == -1) return -1;
#define PUT_CHAR(x)   if (put_char(x) == -1) return -1;

namespace xpp
{
	namespace net
	{
		using namespace xpp::bas;

		SttpBase::SttpBase (Transmittable* s, Address* a): ProtoBase (s, a)
		{
			reset ();
		}
	
		SttpBase::~SttpBase ()
		{
		}

		void SttpBase::reset ()
		{
			send_timeout    = -1;
			receive_timeout = -1;
		
			inbuf_len       = 0;
			outbuf_len      = 0;
			sttp_curp       = 0;
			sttp_curc       = XP_CHAR('\0');
		
			receive_cancel_requested = false;
		}
		
		int SttpBase::get_char (xp_cint_t* ch)
		{
			xp_size_t remain = 0;

			if (sttp_curp == inbuf_len) {
				xp_ssize_t n; 
				xp_time_t cnt;
		#ifdef XP_CHAR_IS_WCHAR
			get_char_utf8:
		#endif
				cnt = 0;
				p_medium->enableTimeout (1000);

				for (;;) {
					n = p_medium->receive (
						&inbuf[remain], xp_countof(inbuf) - remain);
					if (n != -1 ||
					    p_medium->errorCode() != Transmittable::ERR_TIMEDOUT) break;

					if (receive_cancel_requested || canCancelReceive()) {
						p_errcode = ERR_CANCEL;
						return -1;
					}
		
					if (receive_timeout != -1) {
						cnt += 1000;
					 	if (cnt >= receive_timeout) break;
					}
				}
		
				if (n == -1 && p_medium->errorCode() == Transmittable::ERR_TIMEDOUT) {

					p_errcode = ERR_TIMEDOUT;
					return -1;
				}
				if (n <= 0) {
					p_errcode = ERR_RECEIVE;
					return -1;
				}
		
				/*
				if (n == 0) {
					p_errcode = ERR_RECEIVE; // connection closed
					return -1;
				}
				*/
		
				sttp_curp = 0;
				inbuf_len = (xp_size_t)n + remain;
			} 
		
		#ifdef XP_CHAR_IS_WCHAR
			xp_size_t seqlen = xp_utf8_sequence_len (inbuf[sttp_curp]);
			if (seqlen == 0) {
				sttp_curp++; // skip one byte
				p_errcode = ERR_UTF8_CONV;
				return -1;
			}

			remain = inbuf_len - sttp_curp;
			if (remain < seqlen) {
				// must read further...
				xp_memcpy (inbuf, &inbuf[sttp_curp], remain);
				sttp_curp = 0;
				inbuf_len = remain;
				goto get_char_utf8;
			}

			xp_wchar_t wch;
			xp_size_t n = xp_utf8_to_unicode (&inbuf[sttp_curp], seqlen, &wch);
			if (n == 0) {
				sttp_curp++; // still must skip a character
				p_errcode = ERR_RECEIVE;
				return -1;	
			}

			sttp_curc = wch;
			sttp_curp += n;
		#else
			sttp_curc = inbuf[sttp_curp++];
		#endif

			if (ch != XP_NULL) *ch = sttp_curc;
			return 0;
		}
		
		int SttpBase::put_char (xp_cint_t ch)
		{
			xp_assert (xp_sizeof(outbuf) > 0);
		
		#ifdef XP_CHAR_IS_WCHAR
			xp_mchar_t buf[10];
			xp_size_t len = xp_unicode_to_utf8 (ch, buf, xp_countof(buf));
			if (len == 0) {
				p_errcode = ERR_UTF8_CONV;
				return -1;
			}

			for (xp_size_t i = 0; i < len; i++) {
				if (put_mchar(buf[i]) == -1) return -1;
			}
			return 0;
		#else
			return put_mchar (ch);
		#endif
		}

		int SttpBase::put_mchar (xp_mchar_t ch)
		{
			outbuf[outbuf_len++] = ch;
			if (outbuf_len >= xp_countof(outbuf)) return flush_outbuf ();
			return 0;
		}	

		int SttpBase::flush_outbuf ()
		{
			if (outbuf_len > 0) {
				p_medium->enableTimeout (send_timeout);

				xp_ssize_t n = p_medium->send (outbuf, outbuf_len);
				if (n == -1 && p_medium->errorCode() == Transmittable::ERR_TIMEDOUT) {
					p_errcode = ERR_TIMEDOUT;			
					return -1;
				}
				//if (n <= 0) {
				if (n != (xp_ssize_t)outbuf_len) {
					p_errcode = ERR_SEND;			
					return -1;
				}

				outbuf_len = 0;
			}
		
			return 0;
		}
		
		const xp_char_t* SttpBase::errorStr () const
		{
			switch (p_errcode) {
			case ERR_CANCEL: 
				return XP_TEXT("cancelled");
			case ERR_TIMEDOUT:
				return XP_TEXT("timed out");
			case ERR_UTF8_CONV:
				return XP_TEXT("utf8 conversion error");
			default:
				return ProtoBase::errorStr ();
			}
		}

	}
}
